package basicutil.dynamic;

import lombok.Getter;
import lombok.SneakyThrows;

import javax.tools.JavaFileObject;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class JdkDynamicCompileClassLoader extends URLClassLoader {
    private final Map<String, JavaFileObject> javaFileObjectMap = new ConcurrentHashMap<>();
    private final Map<String, Class> loadedClass = new ConcurrentHashMap<>();
    @Getter
    String mainClassName;

    public JdkDynamicCompileClassLoader() {
        super(new URL[0], Thread.currentThread().getContextClassLoader());
    }

    // jar：可填写全路径； class文件：填写到包名即可，不要填class文件全路径！
    @SneakyThrows
    public void addClassPath(String filePath) {
        URL url = Paths.get(filePath).toUri().toURL();
        addURL(url);
    }

    @SneakyThrows
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        JavaFileObject javaFileObject = javaFileObjectMap.get(name);
        if (null != javaFileObject) {
            if (loadedClass.containsKey(name)) {
                return loadedClass.get(name);
            }
            InputStream inputStream = javaFileObject.openInputStream();
            byte[] byteCode = new byte[inputStream.available()];
            inputStream.read(byteCode);
            // 对同一个classname，defineClass不能调用多次，会抛异常attempted  duplicate class definition for name: "org/etry/compile/gui/App"
            Class<?> defineClass = defineClass(name, byteCode, 0, byteCode.length);
            loadedClass.put(name, defineClass);
            return defineClass;
        }
        return super.loadClass(name);
    }

    @SneakyThrows
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        return super.findClass(name);
    }

    /**
     * 暂时存放编译的源文件对象,key为全类名的别名（非URI模式）,如club.throwable.compile.HelloService
     */
    void addJavaFileObject(String qualifiedClassName, JavaFileObject javaFileObject) {
        if (!qualifiedClassName.contains("$")) {
            mainClassName = qualifiedClassName;
        }
        javaFileObjectMap.put(qualifiedClassName, javaFileObject);
    }

    public Class<?> findAnnotatedClass(Class<? extends Annotation> anno) {
        for (String key : javaFileObjectMap.keySet()) {
            try {
                Class<?> aClass = loadClass(key);
                if (aClass.isAnnotationPresent(anno)) {
                    return aClass;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}